package com.gdust.hzx.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gdust.hzx.model.entity.user.UserInfo;
import com.gdust.hzx.model.vo.common.Result;
import com.gdust.hzx.model.vo.common.ResultCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * ClassName: AuthGlobalFilter
 * Package: com.gdust.hzx.gateway.filter
 * Description: 全局过滤器
 * 添加全局Filter，统一处理会员登录
 *
 * 思路：
 * 查看商品详情等不强制登录，如果需要收藏商品、加入购物车、查看订单、获取当前登录用户信息等则必须登录。
 * 可以在网关里面做判断，根据url规则判断用户必须登录，凡是url规则满足规则的就必须登录
 *
 * @Author syun
 * @Create 2024/12/27 10:12
 * @Version 1.0
 */
@Slf4j
@Component
public class AuthGlobalFilter {
    //注入Redis
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    // 路径匹配工具类，用于支持通配符路径匹配
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * 全局过滤方法，用于校验路径中是否需要登录认证
     *
     * @param exchange ServerWebExchange，包含了请求和响应信息
     * @param chain    GatewayFilterChain，网关过滤器链
     * @return Mono<Void> 异步执行结果
     */
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取请求路径
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();

        // 如果路径匹配到需要登录认证的规则，则进行校验
        //判断路径 /api/**/auth/**，登录校验
        if(antPathMatcher.match("/api/**/auth/**",path)) {
            //登录校验
            UserInfo userInfo = this.getUserInfo(request);
            if(userInfo == null) {
                // 如果用户信息为空，则返回未登录的错误提示
                ServerHttpResponse response = exchange.getResponse();
                return out(response,ResultCodeEnum.LOGIN_AUTH);
            }
        }

        // 如果校验通过或路径不需要校验，则放行请求
        return chain.filter(exchange);
    }

    /**
     * 获取当前用户信息
     * 从请求头中获取token，根据token查询Redis获取用户信息
     *
     * @param request ServerHttpRequest，当前请求对象
     * @return UserInfo 用户信息，如果未登录则返回null
     */
    private UserInfo getUserInfo(ServerHttpRequest request) {
        //从请求头获取token
        String token = "";
        List<String> tokenList = request.getHeaders().get("token");
        if(tokenList != null) {
            token = tokenList.get(0);
        }
        //判断token是否为空
        if(!StringUtils.isEmpty(token)) {
            //根据token查询redis
            String userJson = redisTemplate.opsForValue().get("user:login:" + token);
            if(StringUtils.isEmpty(userJson)) {
                return null;
            } else {
                // 将JSON字符串解析为UserInfo对象
                UserInfo userInfo = JSON.parseObject(userJson, UserInfo.class);
                return userInfo;
            }
        }
        return null;
    }

    /**
     * 定义过滤器的执行顺序
     *
     * @return int 顺序值，值越小优先级越高
     */
    public int getOrder() {
        return 0;
    }


    /**
     * 返回未登录或认证失败的响应结果
     *
     * @param response        ServerHttpResponse，用于构造响应内容
     * @param resultCodeEnum  ResultCodeEnum，错误码枚举
     * @return Mono<Void> 异步执行结果
     */
    private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        // 构造通用的结果对象
        Result result = Result.build(null, resultCodeEnum);
        byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //指定编码，否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

}
